package com.funcourse.demo.business;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;

@Slf4j
@NoArgsConstructor
@AllArgsConstructor
@Data
public class RedisResourceLock {

  Map<String,Object> resourceLockHolder = new HashMap<>();

  String lockPrefix;
  private static final String RESOURCE_LOCK_KEY_VALUE = "locked";

  RedisTemplate redisTemplate;

  AtomicInteger waitingThreads = new AtomicInteger(0);

  public RedisResourceLock(String lockPrefix,RedisTemplate redisTemplate){
    this.redisTemplate = redisTemplate;
    this.lockPrefix = lockPrefix;
  }

  public void releaseResourceLock(String resourceId) {
    log.debug("[resourceLock] lock releasing by thread of resource {}",resourceId);
    //[r1]
    Object lock = resourceLockHolder.get(resourceId);
    //[r2]
    if(null != lock){
      //[r3]
      synchronized (lock){
        //[r4]
        resourceLockHolder.remove(resourceId);
        log.debug("[resourceLock] lock releasing notify all by thread/resource{} {}",resourceId,lock);
        //[r5]
        //[r6]
        redisTemplate.delete(lockPrefix + resourceId);
        lock.notifyAll();
      }
    }else{
      //[r6]
      redisTemplate.delete(lockPrefix + resourceId);
      log.warn("[resourceLock] lock is null should not be here thread/resource {}",resourceId);
    }

    log.debug("[resourceLock] lock releasing Redis key deleted thread/resource {}",resourceId);
  }

  public void tryToGetResourceLockBlocking(String resourceId) {
    final long methodStartTimeMillis = System.currentTimeMillis();
    BoundValueOperations resourceLockOps = redisTemplate
        .boundValueOps(lockPrefix + resourceId);
    //[g1]
    Boolean lockGot = resourceLockOps.setIfAbsent(RESOURCE_LOCK_KEY_VALUE, Duration.ofSeconds(1));
    log.debug("[resourceLock] lock got result of thread / resource {} is {}",resourceId,lockGot);
    //first 或者after [r6]
    if (lockGot) {
      //[g2] 因为first 或者after [r6] locks 里面 resourceId对应value一定是空
      resourceLockHolder.put(resourceId, UUID.randomUUID().toString());
      log.debug("time11 is {}",System.currentTimeMillis() - methodStartTimeMillis);
      log.debug("[resourceLock] lock put to map thread / resource {}",resourceId);
    } else {
      try {
        //[g3] after [g1] 可能早于 [g2];[r1]~[r5]
        Object lock = resourceLockHolder.get(resourceId);
        log.debug("[resourceLock] lock object got {} of {}",lock,resourceId);
        synchronized (lock){
          Object lock2 = resourceLockHolder.get(resourceId);
          if(null == lock2 || !lock.equals(lock2)){
            log.debug("[resourceLock] other thread already changes this room lock retry to get null or change {}",null == lock2);
            tryToGetResourceLockBlocking(resourceId);
            return;
          }
          log.debug("time13 is {}",System.currentTimeMillis() - methodStartTimeMillis);
          final long timeStamp = System.currentTimeMillis();
          log.debug("[resourceLock] waiting lock release thread / resource at {} {} {}",resourceId,
              timeStamp,waitingThreads.incrementAndGet());
          lock.wait(2000);
        }
        log.debug("[resourceLock] waiting time {} {}",System.currentTimeMillis() - methodStartTimeMillis,waitingThreads.decrementAndGet());
      } catch (InterruptedException e) {
        log.error("[resourceLock] thread interrupted while waiting lock release", e);
      } catch (NullPointerException npe){
        //before [r4];若早于 [g2];[r1]~[r5] OK
        log.debug("[resourceLock] lock object got synchronized null while redis lock not got");
      }
      tryToGetResourceLockBlocking(resourceId);
    }
  }

}
